﻿using System;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;

namespace Common.Activities
{
    [Designer("System.Activities.Core.Presentation.SequenceDesigner, System.Activities.Core.Presentation")]
    public class UserStepActivity : NativeActivity, INotifyPropertyChanged
    {
        [RequiredArgument]
        public InArgument<string> BookmarkName { get; set; }

        [RequiredArgument]
        public InArgument<bool> ReRunnable { get; set; }

        [RequiredArgument]
        public InArgument<bool> VisibleStep { get; set; }

        [RequiredArgument]
        public InArgument<bool> CanSetStatus { get; set; }

        [Browsable(false)]
        public UserStepStatus Status
        {
            get { return _status; }
            set
            {
                if (_status == value) return;
                _status = value;
                RaisePropertyChanged("Status");
            }
        } UserStepStatus _status;

        [Browsable(false)]
        public Sequence InnerSequence { get; private set; }

        [Browsable(false)]
        public int? Index { get; set; }

        [Browsable(false)]
        public int RunCount { get; private set; }

        [Browsable(false)]
        public int FaultCount { get; private set; }

        [Browsable(false)]
        public int SuccessCount { get; private set; }

        [Browsable(false)]
        public string BookmarkNameValue
        {
            get
            {
                string bookmarkNameValue = null;
                if(this.BookmarkName != null && this.BookmarkName.Expression != null) 
                    bookmarkNameValue = this.BookmarkName.Expression.ToString();

                return bookmarkNameValue;
            }
        }

        [Browsable(false)]
        public Collection<Activity> Activities
        {
            get
            {
                return InnerSequence.Activities;
            }
        }

        [Browsable(false)]
        public Collection<Variable> Variables
        {
            get
            {
                return InnerSequence.Variables;
            }
        }

        public UserStepActivity()
        {
            this.Status = UserStepStatus.Initialized;    
            this.InnerSequence = new Sequence();
            this.RunCount = 0;
            this.FaultCount = 0;
            this.SuccessCount = 0;
        }

        public bool UpdateStatus(UserStepStatus newStatus, out string resultMessage)
        {
            bool updateResult = false;
            resultMessage = default(string);            
            // Can this step be overridden?
            if (Helper.ConvertArgumentToBool(this.CanSetStatus) == true)
            {
                // TODO: define valid status transitions
                // e.g. Errored => CompleteWithErrors
                switch (newStatus)
                {
                    case UserStepStatus.Completed:
                        switch (this.Status)
                        {
                            case UserStepStatus.Initialized:
                            case UserStepStatus.Started:
                            case UserStepStatus.Suspended:
                                this.Status = UserStepStatus.Completed;
                                updateResult = true;
                                break;
                            default:
                                break;
                        }
                        break;
                    case UserStepStatus.CompletedWithErrors:
                        switch (this.Status)
                        {
                            case UserStepStatus.Errored:
                                this.Status = UserStepStatus.CompletedWithErrors;
                                updateResult = true;
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        break;
                }

                // if we didn't make the update, set the result message
                if (updateResult == false)
                {
                    resultMessage =
                        string.Format(
                            "Cannot update activity sequence bookmark: '{0}' from status: {1} to {2} (Invalid Transition)",
                            this.BookmarkName.Expression.ToString(), this.Status.ToString(),
                            newStatus.ToString());
                }
            }
            else
            {
                resultMessage = string.Format("Cannot update activity bookmark: '{0}' (CanSetStatus == false)",
                                              this.BookmarkName.Expression.ToString());
            }

            return updateResult;
        }

        // TODO: remove, only needed for resume bookmark func
        #region Verify If Needed
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            metadata.AddImplementationChild(InnerSequence);
        }

        // NativeActivity derived activities that do asynchronous operations by calling 
        // one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext 
        // must override the CanInduceIdle property and return true.
        protected override bool CanInduceIdle
        {
            get { return true; }
        }
        #endregion

        protected override void Execute(NativeActivityContext context)
        {
            // wrapping sequence has validated execution already
            this.RunCount++;
            Debug.Write(GetStatusMessage());
            context.ScheduleActivity(InnerSequence, OnCompleted, OnFaulted);
        }

        public string GetStatusMessage()
        {
            return string.Format("Activity:\t\t'{0}'\r\nBookmark:\t\t'{1}'\r\nVisible step:\t\t{2}\r\nRe-runnable:\t\t{3}\r\nCan set status:\t\t{4}\r\n Status:\t\t'{5}'\r\nRun/Success/Fault:\t\t{6}/{7}/{8}",
                                 this.DisplayName,
                                 this.BookmarkName.Expression.ToString(),
                                 Helper.ConvertArgumentToBool(this.VisibleStep).ToString(),
                                 Helper.ConvertArgumentToBool(this.ReRunnable).ToString(),
                                 Helper.ConvertArgumentToBool(this.CanSetStatus).ToString(),
                                 this.Status.ToString(),
                                 this.RunCount.ToString(),
                                 this.SuccessCount.ToString(),
                                 this.FaultCount.ToString());
        }

        private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
        {
            this.Status = UserStepStatus.Errored;
            this.FaultCount++;
            Debug.Write(GetStatusMessage());
        }

        private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)
        {
            this.Status = UserStepStatus.Completed;
            this.SuccessCount++;
            Debug.Write(GetStatusMessage());
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Method used to raise an event")]
        void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged == null) return;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}
